/** * Java Diagram Package; An extremely flexible and fast multipurpose diagram component for Swing. Copyright (C) 2001 Eric Crahen <crahen@cse.buffalo.edu> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package diagram; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.HashMap; import javax.swing.JComponent; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.UIManager; /** * @class Diagram * * @date 08-20-2001 * @author Eric Crahen * @version 1.0 * * This is the main component used in the diagram package. It represents the visible * surface that will be displayed in an application and used as a canvas that displays * a DiagramModel. */ public class Diagram extends JComponent implements Scrollable { // UI Class private static final String uiClassID = "DiagramUI"; // Data models private SelectionModel selectionModel; private DiagramModel diagramModel; // Cache a rectangle (for repainting) private Rectangle figureBounds = new Rectangle(); // Renderer and Editor maps private HashMap rendererMap = new HashMap(); private HashMap editorMap = new HashMap(); private boolean fastRefresh; static { UIManager.put("DiagramUI", "diagram.DiagramUI"); } /** * Create a new surface with double buffering */ public Diagram() { this(new DefaultDiagramModel()); } /** * Create a new surface * * @param DiagramModel model */ public Diagram(DiagramModel model) { this(model, new DefaultSelectionModel()); } /** * Create a new surface * * @param DiagramModel model */ public Diagram(DiagramModel model, SelectionModel selectionModel) { this(model, selectionModel, true); } /** * Create a new surface * * @param DiagramModel model * @param boolean buffering */ public Diagram(DiagramModel model, SelectionModel selectionModel, boolean doubleBuffered) { if(model == null || selectionModel == null) throw new IllegalArgumentException(); setDoubleBuffered(doubleBuffered); updateUI(); setOpaque(true); setModel(model); setSelectionModel(selectionModel); enableFastRefresh(true); } /** * Set the UI delegate for this Diagram * * @return DiagramUI */ public DiagramUI getUI() { return (DiagramUI)ui; } /** * Returns a string that specifies the name of the l&f class that renders this component */ public String getUIClassID() { return uiClassID; } /** * Set the UI delegate for this Diagram * * @param DiagramUI */ public void setUI(DiagramUI ui) { super.setUI(ui); } /** * Notification from the UIFactory that the ComponentUI has changed */ public void updateUI() { setUI((DiagramUI)UIManager.getUI(this)); } /** * Set the DiagramModel * * @param DiagramModel */ public void setModel(DiagramModel diagramModel) { DiagramModel oldValue = this.diagramModel; this.diagramModel = diagramModel; firePropertyChange("model", oldValue, diagramModel); } /** * Get the DiagramModel * * @return DiagramModel */ public DiagramModel getModel() { return diagramModel; } /** * Set the SelectionModel that is used internally to keep track of which * items have been selected. * * @param SelectionModel */ public void setSelectionModel(SelectionModel selectionModel) { SelectionModel oldValue = this.selectionModel; this.selectionModel = selectionModel; firePropertyChange("selectionModel", oldValue, selectionModel); } /** * Get the SelectionModel that contains the currently selected items * * @return SelectionModel */ public SelectionModel getSelectionModel() { return selectionModel; } /** * Get this surfaces rendering component. This will be used to * paint each of the Figures that are contained within each * layer. * * @param Class * * @return FigureRenderer */ public FigureRenderer getFigureRenderer(Class itemClass) { for(Object o = null; o == null && itemClass != null; itemClass = itemClass.getSuperclass()) { if((o = rendererMap.get(itemClass)) != null) return (FigureRenderer)o; } return null; } /** * Set the renderer for a particular class of Figures. * * @param Class * @param FigureRenderer */ public void setFigureRenderer(Class itemClass, FigureRenderer renderer) { if(renderer == null) rendererMap.remove(itemClass); else rendererMap.put(itemClass, renderer); } /** * Get this surfaces rendering component. This will be used to * paint each of the Figures that are contained within each * layer. * * @param Class * * @return FigureEditor */ public FigureEditor getFigureEditor(Class itemClass) { for(Object o = null; o == null && itemClass != null; itemClass = itemClass.getSuperclass()) { if((o = editorMap.get(itemClass)) != null) return (FigureEditor)o; } return null; } /** * Set the editor for a particular class of Figures. * * @param Class * @param FigureEditor */ public void setFigureEditor(Class itemClass, FigureEditor editor) { if(editor == null) editorMap.remove(itemClass); else editorMap.put(itemClass, editor); } /** * Find the Figure at the given point. * * @param Point2D * * @return Figure or null */ public Figure findFigure(Point2D pt) { return (ui == null) ? null : ((DiagramUI)ui).findFigure(pt); } /** * Enable fast refresh. This forces repaints more often, good for * faster machines. * * @param boolean */ public void enableFastRefresh(boolean flag) { if(flag != fastRefresh) { boolean last = fastRefresh; fastRefresh = flag; firePropertyChange("fastRefresh", (last ? "true":"false"), (last ? "false":"true")); } } /** * Test to see if fast refresh is enabled. * * @return boolean */ public boolean isFastRefreshEnabled() { return fastRefresh; } /** * Get the extended bounds for a Figure, these are bounds that a component should use * to include a small area for decoration, such as different borders, or arrow heads * that fall just outside the Figures normal bounds. * * @param Figure * @param Rectangle2D reuse a rectangle * * @return Rectangle2D */ public Rectangle2D getDecoratedBounds(Figure figure, Rectangle2D rcBounds) { // Get the renderer for this Figure FigureRenderer renderer = null; if(figure == null || (renderer = getFigureRenderer(figure.getClass())) == null) return rcBounds; // Return the suggested bounds for this figure return (rcBounds = renderer.getDecoratedBounds(this, figure, rcBounds)); } // Scrollable implementation private void checkScrollableParameters(Rectangle visibleRect, int orientation) { if (visibleRect == null) throw new IllegalArgumentException("visibleRect must be non-null"); switch (orientation) { case SwingConstants.VERTICAL: case SwingConstants.HORIZONTAL: break; default: throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL"); } } /** * Returns the preferred size of the viewport for a view component. */ public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } /** * Components that display logical rows or columns should compute the scroll increment * that will completely expose one new row or column, depending on the value of * orientation. * * Set a minmum scroll size of 4 pixels in either direction */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { checkScrollableParameters(visibleRect, orientation); return 4; } /** * Components that display logical rows or columns should compute the scroll increment * that will completely expose one block of rows or columns, depending on the value * of orientation. * * Allow block scrolls as big as the request made */ public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { checkScrollableParameters(visibleRect, orientation); return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width; } /** * Return true if a viewport should always force the width of this Scrollable to match * the width of the viewport. */ public boolean getScrollableTracksViewportWidth() { if(getParent() instanceof JViewport) return (((JViewport)getParent()).getWidth() > getPreferredSize().width); return false; } /** * Components that display logical rows or columns should compute the scroll increment * that will completely expose one new row or column, depending on the value of orientation. */ public boolean getScrollableTracksViewportHeight() { if(getParent() instanceof JViewport) return (((JViewport)getParent()).getHeight() > getPreferredSize().height); return false; } }